package com.gitee.loyo.act

import com.gitee.loyo.KSQL
import com.gitee.loyo.Utils
import com.gitee.loyo.act.Clause.*
import com.gitee.loyo.interfaces.LazyReadMapper
import kotlin.reflect.KMutableProperty0

class ModelOperator<M : Model<M, R>, R : Record<M, R>>(
    val transferorFactory: TransferorFactory = JDBCTransferorFactory
){

    open fun find(model: M, fn: (M.() -> Unit)?): List<R> {
        return findResult(model, fn).asList(model.recordKClass)
    }

    open fun findResult(model: M, fn: (M.() -> Unit)?): LazyReadMapper {
        model.enable(Select, Join, Where, Group, Having, Order)
        model.appendWhereClause{
            fn?.invoke(this)
        }

        return transferorFactory.transfer(model).asLazyReadMapper()
    }

    open fun find(model: M, param: R): List<R>{
        val valueMap = toValueMap(param)
        return find(model){
            valueMap.forEach{ (k, v) ->
                this.getColumn(k)?.also { it eq v }
            }
        }
    }

    open fun findOne(model: M, fn: (M.() -> Unit)?): R? {
        return findResult(model, fn).`as`(model.recordKClass)
    }

    open fun findOne(model: M, param: R): R?{
        val valueMap = toValueMap(param)
        return findOne(model){
            valueMap.forEach{ (k, v) ->
                this.getColumn(k)?.also { it eq v }
            }
        }
    }

    open fun findByID(model: M, value: Any): R? {
        return findOne(model) {
            getPrimaryKey() eq value
        }
    }

    open fun findByIDs(model: M, fn: M.() -> Array<Pair<Column<out Any>, Any>>): R? {
        return findOne(model){
            fn(this).forEach {
                (it.first as Column<Any>) eq it.second
            }
        }
    }

    open fun findForUpdate(model: M, fn: (M.() -> Unit)?): List<R> {
        return findInLock(Lock.FOR_UPDATE, model, fn)
    }

    open fun findInShareMode(model: M, fn: (M.() -> Unit)?): List<R> {
        return findInLock(Lock.IN_SHARE_MODE, model, fn)
    }

    private fun findInLock(lock: Lock, model: M, fn: (M.() -> Unit)?): List<R> {
        model.lock = lock
        return findResult(model, fn).asList(model.recordKClass)
    }

    open fun count(model: M, fn: (M.() -> Unit)?): Int{
        model.enable(Join, Where, Group, Having)
        model.appendWhereClause{
            fn?.invoke(this)
        }

        model.enable(Select)
        model.addToSelectClause(model.count(Column.all(model)))

        return transferorFactory.transfer(model).asLazyReadMapper().`as`(Int::class)?:0
    }

    open fun count(model: M, param: R): Int{
        val valueMap = toValueMap(param)
        return count(model){
            valueMap.forEach{ (k, v) ->
                this.getColumn(k)?.also { it eq v }
            }
        }
    }

    open fun firstOrNull(model: M, fn: (M.() -> Unit)?): R? {
        return take(model, 1, fn).firstOrNull()
    }

    open fun lastOrNull(model: M, fn: (M.() -> Unit)?): R?{
        return last(model, 1, fn).firstOrNull()
    }

    open fun take(model: M, n: Int, fn: (M.() -> Unit)?): List<R> {
        model.enable(Select, Join, Where, Group, Having)
        model.appendWhereClause{
            fn?.invoke(this)
        }
        model.enable(Order)
        for (primaryKey in model.getPrimaryKeys()) {
            model.addToOrderClause(primaryKey, "asc")
        }
        model.limit(n)

        return transferorFactory.transfer(model).asLazyReadMapper().asList(model.recordKClass)
    }

    open fun last(model: M, n: Int, fn: (M.() -> Unit)?): List<R> {
        model.enable(Select, Join, Where, Group, Having)
        model.appendWhereClause{
            fn?.invoke(this)
        }
        model.enable(Order)
        for (primaryKey in model.getPrimaryKeys()) {
            model.addToOrderClause(primaryKey, "desc")
        }
        model.limit(n)

        return transferorFactory.transfer(model).asLazyReadMapper().asList(model.recordKClass)
    }

    open fun add(model: M, record: R): Boolean{
        buildInsertClause(model, record)
        return transferorFactory.transfer(model).insert() == 1
    }

    open fun create(model: M, record: R): Any? {
        buildInsertClause(model, record)
        return transferorFactory.transfer(model).create()
    }

    private fun buildInsertClause(model: M, record: R){
        model.enable(InsertInto)
        val propValues = KSQL.reflection.getClassParameters(record::class).mapNotNull {
            val value = KSQL.reflection.getFieldValue(record, it.name!!) ?: return@mapNotNull null
            val columnName = KSQL.nameMappingStrategy.toColumnName(it.name!!)
            val column = KSQL.reflection.getFieldValue(model, columnName) ?: return@mapNotNull null
            (column as Column<Any>) to value
        }.toList()
        model.appendInsertIntoClause(propValues)
    }

    open fun update(model: M, record: R, fn: (M.() -> Unit)? = null): Boolean {
        val columnMap = KSQL.reflection.getClassParameters(model::class).map {
            it.name!! to (KSQL.reflection.getFieldValue(model, it.name!!) as Column<Any>)!!
        }.toMap()
        val primaryKeyMap = record.getPrimaryKeyValueMap()
        val setPropValues = KSQL.reflection.getClassParameters(record::class).map {
            val columnName = KSQL.nameMappingStrategy.toColumnName(it.name!!)
            if(primaryKeyMap.containsKey(columnName)){
                null
            }else{
                columnMap[columnName] to KSQL.reflection.getFieldValue(record, it.name!!)
            }
        }.filterNotNull().toTypedArray() as Array<Pair<Column<Any>, Any?>>

        model.enable(Where)
        fn?.invoke(model)

        return update(model){
            set(*setPropValues)
            where {
                primaryKeys.ifEmpty { null }?.forEach {
                    it eq (primaryKeyMap[it.fieldName]?:throw NullPointerException("${record::class.simpleName}.${it.fieldName}为null值"))
                }?:throw UnsupportedOperationException("${model::class.simpleName}未发现主键")
            }
        }
    }

    open fun update(model: M, fn: M.() -> Unit): Boolean {
        model.enable(UpdateSet, Where)
        fn(model)
        return transferorFactory.transfer(model).update() == 1
    }

    open fun remove(model: M, fn: (M.() -> Unit)?): Int{
        model.enable(Where)
        model.appendWhereClause {
            fn?.invoke(this)
        }

        return transferorFactory.transfer(model).delete()
    }

    private fun toValueMap(param: R): Map<String, Any>{
        return KSQL.reflection.toMap(param){
            Utils.isNotObject(it.type.kotlin)
        }.filter {
            it.value != null
        }.toMap() as Map<String, Any>
    }
}